package com.gitee.zou8944.ipersist.utils

import com.gitee.zou8944.ipersist.core.bean.Configuration
import com.gitee.zou8944.ipersist.core.bean.SqlStatement
import com.gitee.zou8944.ipersist.core.bean.SqlType
import com.gitee.zou8944.ipersist.kotlin.Tuple5
import org.dom4j.Node
import org.dom4j.io.SAXReader
import java.io.InputStream

object XmlReader {

    fun readCoreConfig(inputStream: InputStream): Configuration {
        val document = SAXReader().read(inputStream)
        // 读取数据源
        val dataSourceProperties = document.selectNodes("//configuration/datasource/property")
            .map { it as Node }
            .map { it.valueOf("@name") to it.valueOf("@value") }
            .toMap()

        // 遍历所有mapper
        val mappedStatementPair = document.selectNodes("//configuration/mappers/mapper")
            .map { it as Node }
            .map {
                val mapperPath = it.valueOf("@resource")
                val mapperInputStream = Resources.getResourceAsInputStream(mapperPath)
                readMapperConfig(mapperInputStream).toList()
            }
            .flatten()

        // 得判断statement id是否有重复得情况
        mappedStatementPair
            .groupBy { it.first }
            .filter { it.value.size > 1 }
            .takeIf { it.isNotEmpty() }
            ?.run {
                throw RuntimeException("Duplicate mapper statement id: ${this.keys}")
            }

        val mappedStatement = mappedStatementPair.toMap()

        return Configuration(dataSourceProperties, mappedStatement)
    }

    fun readMapperConfig(inputStream: InputStream): Map<String, SqlStatement> {
        val document = SAXReader().read(inputStream)
        val namespace = document.rootElement.attribute("namespace").value

        val xpaths = listOf(
            "//mapper/select",
            "//mapper/update",
            "//mapper/insert",
            "//mapper/delete"
        )

        return xpaths
            .map { xpath -> document.selectNodes(xpath) }
            .flatten()
            .map { it as Node }
            .map { node ->
                val (id, parameterType, resultType, sql, sqlType) = Tuple5(
                    node.valueOf("@id"),
                    node.valueOf("@parameterType"),
                    node.valueOf("@resultType"),
                    node.stringValue.trim(),
                    SqlType.from(node.name)
                )
                val finalId = "$namespace.$id"
                val sqlStatement = SqlStatement(
                    sql = sql,
                    sqlType = sqlType,
                    parameterType = parameterType.takeIf { it.isNotBlank() }?.let { Class.forName(it) },
                    resultType = resultType.takeIf { it.isNotBlank() }?.let { Class.forName(it) }
                )
                finalId to sqlStatement
            }
            .toMap()
    }
}
